#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "UnCopy.hpp"
#include "Log.hpp"
#include "Commen.hpp"
#include "InetAddress.hpp"

static const uint16_t default_port = 9090;
static const int default_fd = -1;
static const int default_buffer_size = 1024;

class UdpServer : public UnCopy
{
public:
    UdpServer(uint16_t port = default_port)
        : _port(port), _socket_fd(default_fd)
    {}

    void init()
    {
        // 1.创建socket
        //AF_INET使用TCP/IPv4协议栈/协议家族，SOCK_DGRAM使用数据报套接，协议类型有前两参数即可确定，缺省为0
        _socket_fd = socket(AF_INET, SOCK_DGRAM, 0); 
        if(_socket_fd < 0)
        {
            log.message(FATAL, "socket error, %d: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        log.message(INFO, "socket success, socket fd: %d", _socket_fd);

        // 2.填写本端网络信息
        struct sockaddr_in local; // 套接地址，内含ip+port (C风格继承，是sockaddr子类，in表示inet，即internet网络)
        bzero(&local, sizeof(local)); //初始化为全0
        local.sin_family = AF_INET; //设置协议家族
        local.sin_port = htons(_port); //本进程使用的端口，htons将_port转为网络序列
        local.sin_addr.s_addr = INADDR_ANY; //本进程使用的ip，多网卡服务器绑定本机所有ip

        // 3.绑定 网络信息sockaddr 与 文件信息socket

        // socket是插座，是网卡的软件封装，有发送和接收网络数据的能力
        // 网络信息ip+port，是进程在网络上的唯一标识
        // 向socket插入ip+port（绑定网络信息），就赋予了进程向网络发送和接收数据的能力

        // 服务器端口使用情况应由程序员管理，要手动指定并绑定给socket
        // 一台服务器可能配有多块网卡，即有多个ip，绑定本机所有ip，可以监听到所有ip上的所需端口
        int n = bind(_socket_fd, (struct sockaddr*)&local, sizeof(local)); //C风格向上转型
        if(n != 0)
        {
            log.message(FATAL, "bind error, %d: %s\n", errno, strerror(errno));
            exit(BIND_ERR);
        }

        // 4.填写对端网络信息
        //通信双方必须知道对方的网络信息，只有请求到来了，服务端才知道目标主机是谁，在接收时填写（输出型参数）
    }

    void start()
    {
        char buffer[default_buffer_size];
        while(true)
        {
            struct sockaddr_in peer; //对端信息，输出型参数
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_socket_fd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
            if(n > 0)
            {
                buffer[n] = '\0';
                InetAddress address(peer);
                std::cout << address.getAddressInfo() << buffer << std::endl;
                sendto(_socket_fd, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len);
            }
        }
    }

    ~UdpServer()
    {}
private:
    uint16_t _port;
    int _socket_fd;
};